﻿Imports System.Threading.Tasks

Public Class MultipleFilesInputViewModel
    Inherits RegexInputViewModelBase

#Region "Events"
    ' Insert Event Declarations Here
#End Region

#Region "Properties"
#Region "Constants"
    ' Insert Constant Declarations Here
#End Region

    Private WithEvents _input As IMultipleFilesInput
    Protected Property Input As IMultipleFilesInput
        Get
            Return _input
        End Get
        Private Set(value As IMultipleFilesInput)
            _input = value
        End Set
    End Property

    Private Const PROPERTY_ROOT_DIRECTORY_PATH As String = "RootDirectoryPath"
    Public Property RootDirectoryPath As String
        Get
            Return Me.Input.RootDirectoryPath
        End Get
        Set(value As String)
            Me.Input.RootDirectoryPath = value
        End Set
    End Property

    Private Const PROPERTY_INCLUDE_SUBDIRECTORIES As String = "IncludeSubDirectories"
    Public Property IncludeSubDirectories() As Boolean
        Get
            Return Me.Input.IncludeSubDirectories
        End Get
        Set(ByVal value As Boolean)
            Me.Input.IncludeSubDirectories = value
        End Set
    End Property

    Private Const PROPERTY_FILE_NAME_FILTER As String = "FileNameFilter"
    Public Property FileNameFilter As String
        Get
            Return Me.Input.FileNameFilter
        End Get
        Set(value As String)
            Me.Input.FileNameFilter = value
        End Set
    End Property

    Private _matchingFiles As IEnumerable
    Public Property MatchingFiles() As IEnumerable
        Get
            Return _matchingFiles
        End Get
        Set(ByVal value As IEnumerable)
            If _matchingFiles IsNot value Then
                _matchingFiles = value
                MyBase.OnPropertyChanged()
            End If
        End Set
    End Property

    Public Overrides ReadOnly Property HeaderText As String
        Get
            Return "Find in Files"
        End Get
    End Property

    Friend Const PROPERTY_SELECTED_FILE As String = "SelectedFile"
    Private _selectedFile As Object
    Public Property SelectedFile() As Object
        Get
            Return _selectedFile
        End Get
        Set(ByVal value As Object)
            If value IsNot _selectedFile Then
                _selectedFile = value
                MyBase.OnPropertyChanged()
            End If
        End Set
    End Property

#End Region

#Region "Constructors"
    Public Sub New(input As IMultipleFilesInput, parent As MainViewModel, viewNavigator As IViewNavigator)
        MyBase.New(parent, viewNavigator)

        Me.Input = input

        Me.AddCommand(
            "Browse",
            Sub(parameter As Object)
                Dim l_handle = Me.ViewNavigator.GetDirectoryHandle()
                If l_handle IsNot Nothing Then
                    Me.RootDirectoryPath = l_handle.FullName
                End If
            End Sub)

        Me.AddCommand(
            "Execute",
            AddressOf Execute)

        Me.AddCommand(
            "ViewMatches",
            Sub(parameter As Object)
                If Me.SelectedFile IsNot Nothing Then
                    Dim l_fileContent As String = Me.SelectedFile.FileContent
                    With viewNavigator.GoToLiteralTextInput(l_fileContent)
                        .Commands("Execute").Execute(Nothing)
                    End With
                End If
            End Sub)
    End Sub
#End Region

#Region "Methods"
    Private Async Sub Execute(parameter As Object)
        Dim l_regex = Me.Parent.CreateRegex("Find in Files")
        If l_regex IsNot Nothing Then
            If IO.Directory.Exists(Me.RootDirectoryPath) Then

                Me.MatchingFiles = Await GetFiles(l_regex, Me.RootDirectoryPath, Me.FileNameFilter)

            Else
                ViewNavigator.DisplayErrorMessage(
                    "Nonexistent Directory",
                    "The specified directory does not exist.",
                    False)
            End If
        End If
    End Sub

    Private Shared Async Function GetFiles(fileContentsRegex As Regex, rootDirectoryPath As String, fileNameFilter As String) As Task(Of IEnumerable)
        Dim l_regex = fileContentsRegex
        If l_regex IsNot Nothing Then

            Dim l_walkDirectory =
                Function(root As IO.DirectoryInfo, recursive As Object, progressCallback As Action(Of String, Integer)) As IEnumerable(Of IO.FileInfo)
                    Dim l_files As New List(Of IO.FileInfo)
                    Try
                        Dim l_subDirectories = root.GetDirectories()
                        For l_subDirectoryIndex = 0 To l_subDirectories.Length - 1
                            Dim l_subDirectory = l_subDirectories(l_subDirectoryIndex)
                            l_files.AddRange(recursive.Invoke(l_subDirectory, recursive, Nothing))

                            If progressCallback IsNot Nothing _
                                Then progressCallback(
                                            "Directories: " & l_subDirectory.Name,
                                            100 * (l_subDirectoryIndex + 1) / l_subDirectories.Length)
                        Next
                    Catch ex As IO.IOException
                    Catch ex As UnauthorizedAccessException
                    End Try

                    Try
                        Dim l_childFiles = root.GetFiles()
                        For l_childFileIndex = 0 To l_childFiles.Length - 1
                            Dim l_childFile = l_childFiles(l_childFileIndex)

                            If Regex.IsMatch(l_childFile.Name, fileNameFilter) _
                                Then l_files.Add(l_childFile)

                            If progressCallback IsNot Nothing _
                                Then progressCallback(
                                            "Files: " & l_childFile.Name,
                                            100 * (l_childFileIndex + 1) / l_childFiles.Length)
                        Next
                    Catch ex As IO.IOException
                    Catch ex As UnauthorizedAccessException
                    End Try

                    Return l_files
                End Function

            Try
                Dim l_dirInfo = New IO.DirectoryInfo(rootDirectoryPath)
                Dim l_candidateFilesTask As New Task(Of IEnumerable(Of IO.FileInfo))(
                    Function() As IEnumerable(Of IO.FileInfo)
                        Return l_walkDirectory(
                            l_dirInfo,
                            l_walkDirectory,
                            Sub(message As String, progress As Integer)
                                ' Do nothing for now
                            End Sub)
                    End Function)
                Dim l_candidateFiles = Await l_candidateFilesTask

                Dim l_filesWithContentTask As New Task(Of IEnumerable(Of Object))(
                    Function() As IEnumerable(Of Object)
                        Return _
                            From f In l_candidateFiles
                            Select
                                FileInfo = f,
                                FileContent = (
                                    Function() As String
                                        Try
                                            Return IO.File.ReadAllText(f.FullName)
                                        Catch ex As IO.IOException
                                            Return Nothing
                                        End Try
                                    End Function).Invoke()
                    End Function)
                Dim l_filesWithContent =
                    Await l_filesWithContentTask

                Dim l_matchingFilesTask As New Task(Of IEnumerable)(
                    Function() As IEnumerable
                        Return _
                            From f In (From f In l_filesWithContent
                                       Select New With {
                                            .FileInfo = f.FileInfo,
                                            .FileContent = f.FileContent,
                                            .Matches = l_regex.Matches(f.FileContent),
                                            .RelativePath = f.FileInfo.FullName.Substring(IO.Path.Combine(rootDirectoryPath, "").Length)
                                        }
                                )
                            Where
                                f.Matches.Count > 0 AndAlso
                                f.Matches.Cast(Of Match).Any(Function(m) m.Success)
                            Order By f.FileInfo.FullName()
                    End Function)
                Dim l_matchingFiles = Await l_matchingFilesTask

                Return l_matchingFiles

            Catch ex As IO.IOException
            Catch ex As UnauthorizedAccessException

            End Try

        End If

        Return Nothing
    End Function

#Region "Event Raiser Methods"
    ' Insert Method Declarations which Raise an Event Here
#End Region

#Region "Event Handlers"
    Private Sub Input_PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Handles _input.PropertyChanged
        Select Case e.PropertyName
            Case "RootDirectoryPath"
                OnPropertyChanged(PROPERTY_ROOT_DIRECTORY_PATH)

            Case "IncludeSubDirectories"
                OnPropertyChanged(PROPERTY_INCLUDE_SUBDIRECTORIES)

            Case "FileNameFilter"
                OnPropertyChanged(PROPERTY_FILE_NAME_FILTER)
        End Select
    End Sub
#End Region
#End Region

End Class
